/* Copyright (C) 2007-2008  klevgert  http://code.google.com/p/db4o-netbeans/

This file is part of the db4o-netbeans module suite.

db4o-netbeans is free software; you can redistribute it and/or modify it under
the terms of version 2 of the GNU General Public License as published
by the Free Software Foundation and as clarified by db4objects' GPL 
interpretation policy, available at
http://www.db4o.com/about/company/legalpolicies/gplinterpretation/
Alternatively you can write to db4objects, Inc., 1900 S Norfolk Street,
Suite 350, San Mateo, CA 94403, USA.

db4o-netbeans is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. */

package com.db4o.nb.api.impl;

import com.db4o.nb.api.Constrain;
import com.db4o.nb.api.Db4oDatabase;
import com.db4o.nb.api.Db4oProvider;
import com.db4o.nb.api.Db4oServer;
import com.db4o.nb.api.Descend;
import com.db4o.nb.api.QueryData;
import com.db4o.nb.api.exception.Db4oSaveException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.openide.filesystems.FileLock;
import org.openide.filesystems.FileObject;
import org.openide.loaders.DataObject;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * This class represents a complete data structure for a 
 * db4o database connection.
 * 
 * @author klevgert
 */
public class Db4oProviderImpl implements Db4oProvider {

  /** db4o database access object */
  private Db4oDatabase  database;
  
  /** list of queries defined on the db4o database */
  private List          queries = new ArrayList();
  
  /** xml document holding the db4o database connection details */
  private Document      document;
  
  /** net beans data object */
  private DataObject dataobj;

  /** server that hosts a database on demand */
  private Db4oServer serverHost;
 
  /**
   * Creates a new instance.
   * @dataobj  data object used by the data node.  
   */
  public Db4oProviderImpl(DataObject dataobj) {

    this.dataobj = dataobj;
    this.load();

  }

  /**
   * instance not needed anymore. 
   * Release resources
   */
  protected void finalize() throws Throwable {
    // Invoke the finalizer of our superclass
    // We haven't discussed superclasses or this syntax yet
    super.finalize();

    this.dataobj = null;
    this.database = null;
    this.serverHost = null;
    this.document = null;
    this.queries = null;
  }
  
  /**
   * Gets the db4o database connection.
   * @return db4o database connection wrap.
   */
  public Db4oDatabase getDatabase() {
    return this.database;
  }

  /**
   * List of defined queries.
   * @return list of query data objects.
   */
  public List getQueries() {
    return this.queries;
  }

  /**
   * Gets a host for a db4o database. Actually it is a small piece that starts as server and opens
   * access to the database. Lazy init.
   * @return db4o server instance.
   */
  public Db4oServer getServerHost() {
    if( this.serverHost == null ) {
      this.serverHost = new Db4oServerImpl( this.database );
    }
    return this.serverHost;
  }
  
  /**
   * creates a sql query data object.
   * @param name        name of query.
   * @param expression  sql expression.
   * @param type        queries object type.
   * @return query object.
   */
  public QueryData createSqlQuery(String name, String expression, String type) {
    
    Element elem = this.document.createElement( Constants.ELEM_QUERY );
    elem.setAttribute( Constants.ATTR_KIND, Constants.ATTR_KIND_SQL);
    
    if( name != null ) {
      elem.setAttribute( Constants.ATTR_NAME, name);
    }
    if( type != null ) {
      elem.setAttribute( Constants.ATTR_TYPE, type);
    }
    if( expression != null ) {
      elem.setAttribute( Constants.ATTR_EXPR, expression);
    }
    
    return new SqlQueryImpl(elem);
  }

  /**
   * creates an empty soda query data object.
   * @param name        name of query.
   * @param type        queries object type.
   * @return query data object.
   */
  public QueryData createSodaQuery(String name, String type) {
    
    Element elem = this.document.createElement( Constants.ELEM_QUERY );
    elem.setAttribute( Constants.ATTR_KIND, Constants.ATTR_KIND_SODA);

    if( name != null ) {
      elem.setAttribute( Constants.ATTR_NAME, name);
    }
    if( type != null ) {
      elem.setAttribute( Constants.ATTR_TYPE, type);
    }
    return new SodaQueryImpl(elem);
  }

  /**
   * creates a descend object to be added to a soda query data object.
   * @param type        queries object type.
   * @param value       value or name of descend.
   * @return descend object.
   */
  public Descend createDescend(String type, String value) {
    Element elem = this.document.createElement( Constants.ELEM_DESCEND );
    if( type != null ) {
      elem.setAttribute( Constants.ATTR_TYPE, type);
    }
    if( value != null ) {
      elem.setAttribute( Constants.ATTR_VALUE, value);
    }
    return new DescendImpl(elem);
  }

  /**
   * creates a constraint object to be added to a soda query data object.
   * @param tag         tagging identifier for the contraint (value, not greater, smaller).
   * @param type        queries object type.
   * @param value       value or name of descend.
   * @return constraint object.
   */
  public Constrain createConstrain(String tag, String type, String value) {
    Element elem = this.document.createElement( Constants.ELEM_CONSTRAIN );
    elem.setAttribute( Constants.ATTR_TAG, tag);
    if( type != null ) {
      elem.setAttribute(Constants.ATTR_TYPE, type);
    }
    if( value != null ) {
      elem.setAttribute(Constants.ATTR_VALUE, value);
    }
    return new ConstrainImpl(elem);
  }

  /**
   * creates a value constraint object to be added to a soda query data object.
   * @param type        queries object type.
   * @param value       value or name of descend.
   * @return constraint object.
   */
  public Constrain createConstrain(String type, String value) {
    return createConstrain( Constants.ATTR_TAG_VALUE, type, value);
  }
  
  /**
   * creates a NOT constraint object to be added to a soda query data object.
   * @return constraint object.
   */
  public Constrain createNot() {
//    Element elem = this.document.createElement("not");
//    return new ConstrainImpl(elem);
    return createConstrain( Constants.ATTR_TAG_NOT, null, null);
  }

  /**
   * creates a GREATER constraint object to be added to a soda query data object.
   * @return constraint object.
   */
  public Constrain createGreater() {
//    Element elem = this.document.createElement("greater");
//    return new ConstrainImpl(elem);
    return createConstrain( Constants.ATTR_TAG_GREATER, null, null);
  }

  /**
   * creates a SMALLER constraint object to be added to a soda query data object.
   * @return constraint object.
   */
  public Constrain createLess() {
//    Element elem = this.document.createElement("less");
//    return new ConstrainImpl(elem);
    return createConstrain( Constants.ATTR_TAG_LESS, null, null);
  }
  
  /**
   * loads the content of the file opened.
   */
  public void load() {
    try {

      FileObject file = this.dataobj.getPrimaryFile();
      InputStream is = file.getInputStream();
      this.document = this.readFile(is);

    } catch (FileNotFoundException ex) {
      ex.printStackTrace();
      throw new Db4oSaveException(ex);
    } catch (SAXException ex) {
      ex.printStackTrace();
      throw new Db4oSaveException(ex);
    } catch (IOException ex) {
      ex.printStackTrace();
      throw new Db4oSaveException(ex);
    } catch (ParserConfigurationException ex) {
      ex.printStackTrace();
      throw new Db4oSaveException(ex);
    }
  }

  /**
   * stores the content of the file opened.
   */
  public void save() {

    Document doc = this.document;
    try {

      Transformer transformer = 
        TransformerFactory.newInstance().newTransformer();
      Source source = new DOMSource( doc );
      FileLock lock = this.dataobj.getPrimaryFile().lock();
      Result output = new StreamResult( this.dataobj.getPrimaryFile().getOutputStream(lock) );
      lock.releaseLock();
      transformer.transform( source, output );
      lock = null;

    } catch (TransformerConfigurationException ex) {
      ex.printStackTrace();
      throw new Db4oSaveException(ex);
    } catch (TransformerException ex) {
      ex.printStackTrace();
      throw new Db4oSaveException(ex);
    } catch (IOException ex) {
      ex.printStackTrace();
      throw new Db4oSaveException(ex);
    } catch (TransformerFactoryConfigurationError ex) {
      ex.printStackTrace();
      throw new Db4oSaveException(ex);
    }
  }
 
  /**
   * loads the content of a file opened.
   * @is         input stream.
   * @return     xml dom document.
   * @throws     ParserConfigurationException
   * @throws     SAXException
   * @throws     IOException
   */
  private Document readFile(InputStream is) throws ParserConfigurationException, SAXException, IOException {

    // Get a dom document builder factory.
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setNamespaceAware(true);
    dbf.setIgnoringElementContentWhitespace(true);

    // Get a dom document builder and parse insput stream.
    Document doc = null;
    DocumentBuilder builder = dbf.newDocumentBuilder();
    doc = builder.parse(is);

    // setup root object of the api
    NodeList nl = doc.getElementsByTagName(Constants.ELEM_DATABASE);
    if( nl.getLength() > 0 ) {

      Element elem = (Element)nl.item(0);
      this.database = new Db4oDatabaseImpl(elem);
      
      NodeList queryNodes = elem.getChildNodes();
      int queryNodesCount = queryNodes.getLength();
      for( int i=0; i<queryNodes.getLength(); i++) {
        
        if( queryNodes.item(i) instanceof Element ) {
          Element queryElements = (Element)queryNodes.item(i);
          if( queryElements.getTagName().equals("queries") ) {
            this.queries = new QueryList(queryElements);
          }
        }
      }
    }

    return doc;
  }
  

}